/* The file is saved in UTF-8 codepage.
 * Check: «Stereotype», Section mark-§, Copyright-©, Alpha-α, Beta-β, Smile-☺
 */
package cz.pecinovsky.english.lootp.shapes;

import cz.pecinovsky.english.lootp.utility.NamedColor;
import cz.pecinovsky.english.lootp.utility.IO;


import static cz.pecinovsky.english.lootp.dbg.ThreadMessages.*;



import cz.pecinovsky.english.lootp.shapes.Canvas_Envelope.  Canvas;
////////////////////////////////////////////////////////////////////////////////
//%%+  +++++ End of ignored starting test - place for imports +++++

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Shape;

import java.awt.geom.Rectangle2D;

import java.io.File;
import java.io.FileReader;
import java.io.Reader;

import java.lang.reflect.InvocationTargetException;

import java.util.Properties;

import javax.swing.JFrame;
import javax.swing.JPanel;

//%X+ xxxxx Start of the ovejumped text with declaration of the envelope xxxxx
/*******************************************************************************
 *<pre>
 * Previous:  No - this is a newly defined type
 * Following: No
 *
 * Project  Ppp
 *   + Adeed
 *   - Removed
 *   ~ Changed
 *</pre>
 *
 * @author Rudolf PECINOVSKÝ
 * @version 6.03.2017 — 2010-11-08
 */
public class Canvas_Envelope
{    private Canvas_Envelope() {}   static
////////////////////////////////////////////////////////////////////////////////
//%X- ----- End of the ovejumped text with declaration of the envelope -----


/*******************************************************************************
 * The instance of class {@code Canvas} (singleton) serves as a virtual canvas,
 * on which the shapes can be painted.
 * <p>
 * The class doesn't offer the public constructor to others
 * because its instance should be a singleton
 * to ensure that all shapes will be painted on the same canvas.
 * The only way of getting the reference to canvas
 * is calling the static factory method {@link getCanvas()}.</p>
 * <p>
 * To allow a simple painting
 * without some registration,
 * the rubbed out parts of the painted shapes are not restored.
 * If therefore is a part of another shape rubbed out as a side effect,
 * the damaged shape should be repainted explicitly.
 *
 * @author   Rudolf PECINOVSKY
 * @version  3.00.001
 */
public final class Canvas
{
//== CONSTANT CLASS ATTRIBUTES =================================================

    /** The starting canvas title. */
    private static final String TITLE  = "Simple canvas";

    /** The stating width in points. */
    public static final int WIDTH_0 = 300;

    /** The starting height in points. */
    public static final int HEIGHT_0 = 300;

    /** The starting canvas color. */
    public static final NamedColor BACKGROUND_0 = NamedColor.CREAMY;



//== VARIABLE CLASS ATTRIBUTES =================================================

    /** The only instance of the canvas. */
    private static Canvas singleton;    // = new Canvas();



//== STATIC CONSTRUCTOR (CLASS INITIALIZER, STATIC INICITALIZING BLOCK) ========
//== CONSTANT INSTANCE ATTRIBUTES ==============================================
//== VARIABLE INSTANCE ATTRIBUTES ==============================================

    //Attributes which values are not directly adjustable by user

        /** The application window of the canvas. */
        private JFrame frame;

        /** Instance of a local class created
         *  for hiding the parents methods of the JPanel. */
        private JPanel canvasPanel;

        /** Everything is drawn on the picture,
         *  which can be easily repainted. */
        private Image canvasImage;

        /** The paint-tool needed for painting on the canvas. */
        private Graphics2D painter;

//        /** Difference between the thickness of the window border announced
//         * before and after calling method {@link #setResizable(boolean)}.
//         * This difference affect in Windows setting of the position and size.
//         * By {@code setResizable(true)} the values a higher
//         * and so we compute it as "true" - "false". */
//        Insets insetsDif;


    //Attributes directly adjustable by user

        /** Color of canvas = bacground color of painting. */
        private NamedColor backgroundColor;

        /** Canvas width in points. */
        private int width;

        /** Canvas height in points. */
        private int height;

        /** Canvas position on the dislay; by using more monitors it is
         *  sometimes necesserry to refresh it after making window visible. */
        Point position = new Point(0, 0);



//== CLASS GETTERS AND SETTERS =================================================
//== OTHER NON-PRIVATE CLASS METHODS ===========================================

    /***************************************************************************
     * Clears the canvas, it means that it erases all the shapes on the canvas.
     * Rightly this method should be defined as instance method,
     * however, because the instance is defined as a singleton
     * the method is defined as static to allow to clear the canvas
     * without obtaining the reference to its instance.
     */
    static public void clearCanvas()
    {
        singleton.clear();
    }



//##############################################################################
//== CONSTUCTORS AND FACTORY METHODS ===========================================

    /***************************************************************************
     * The only method allowing to get reference to instance of canvas.
     * However, because the instance is defined as a singleton,
     * the method returns every time the reference to the same instance.
     *
     * @return The reference to the singleton instance of the class
     */
    public static Canvas getCanvas()
    {
//%A+ <"
        msgS("getCanvas()");
//%A-
        if (singleton == null) {
            synchronized(Canvas.class) {
                if (singleton == null) {
                    singleton = new Canvas();
                }
            }
        }
        singleton.frame.setVisible(true);
//%A+ <"
        msgF("getCanvas()");
//%A-
        return singleton;
    }


    /***************************************************************************
     * Default (and only available) constructor.
     * It is called only from the declaration of the singleton instance.
     */
    private Canvas()
    {
//%A+ <"
        msgS("Constructor Canvas()");
//%A-
        configurationFromFile();
        try {
            EventQueue.invokeAndWait(new Runnable()
            {
                @Override
                public void run() {
//%A+ <"
                    msgS("run()");
//%A-
                    prepareAppWindow();
//%A+ <"
                    msgF("run()");
//%A-
                }
            });
        }
        catch (InterruptedException ex) {
            throw new RuntimeException(
                    "\nApplication window preparation interrupted", ex);
        }
        catch (InvocationTargetException ex) {
            throw new RuntimeException(
                    "\nApplication window preparation did not succeed", ex);
        }

        IO.setDialogsPosition(position.x, position.y + frame.getSize().height);
//%A+ <"
        msgF("Constructor Canvas()");
//%A-
    }


    @SuppressWarnings("serial")         //Because of annonymous class
    private void prepareAppWindow()
    {
//%A+ <"
        msgS("prepareAppWindow()");
//%A-
        frame  = new JFrame();          //Create the new application window
//%A+ <"
        msg("JFrame created");
//%A-
        frame.setLocation(position);
        frame.setTitle(TITLE);

        //Closing the window we close all the application
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        canvasPanel = new JPanel()
        {   /** Obligatory ovirriding the abstract method od JPanel. */
            @Override
            public void paint(Graphics g) {
                g.drawImage(canvasImage, 0, 0, null);
            }
        };
        frame.setContentPane(canvasPanel);
        backgroundColor = BACKGROUND_0;

        setSizePrivate(WIDTH_0, HEIGHT_0); //Prepares and shows an empty canvas
        IO.Correction.windowLocation(frame);
        prepareImage();
        clear();

        IO.setDialogsPosition(position.x, position.y + frame.getSize().height);
//%A+ <"
        msgF("prepareAppWindow()");
//%A-
    }



//== ABSTRACT METHODS ==========================================================
//== INSTANCE GETTERS AND SETTERS ==============================================

    /***************************************************************************
     * Returns the current canvas color (= backgound color).
     *
     * @return Current background color
     */
    public NamedColor getBackgroundColor()
    {
        return backgroundColor;
    }


    /***************************************************************************
     * Set the current canvas color (= backgound color).
     *
     * @param color  The set canvas color
     */
    public void setBackgroundColor(NamedColor color)
    {
        this.backgroundColor = color;
        painter.setBackground(color.getAwtColor());
        clear();
    }


    /***************************************************************************
     * Set the color of painting.
     *
     * @param color Set painting color
     */
    public void setForegroundColor(NamedColor color)
    {
        painter.setColor(color.getAwtColor());
    }


    /***************************************************************************
     * Returns the canvas width in pixels.
     *
     * @return Pixel width of the canvas
     */
    public int getWidth()
    {
        return width;
    }


    /***************************************************************************
     * Returns the canvas height in pixels.
     *
     * @return Pixel height of the canvas
     */
    public int getHeight()
    {
        return height;
    }


    /***************************************************************************
     * Set the new size of the canvas.
     *
     * @param  width   The new canvas width in pixels
     * @param  height  The new canvas height in pixels
     */
    public void setSize(int width, int height)
    {
        setSizePrivate(width, height);
        setVisible(true);
        prepareImage();
        clear();
    }


    /***************************************************************************
     * Indicates whether the canvas application window is visible.
     * However even the visible windows can be hiden by another windows.
     *
     * @return If the window is visible, it returns <b>true</b>,
     *         otherwise it returns <b>false</b>
     */
    public boolean isVisible()
    {
        return frame.isVisible();
    }


    /***************************************************************************
     * Set the visibility of canvas application window.
     *
     * @param visible <b>true</b> if the application window shoud be visible,
     *                otherwise <b>false</b>
     */
    public void setVisible(boolean visible)
    {
        boolean change = (isVisible() != visible);
        if (change)
        {
            frame.setVisible(visible);
            if (visible)
            {
                //With more displays the window doesn't work well - it is
                frame.setLocation(position);  //necessary to set position again
                frame.setAlwaysOnTop(true);
                frame.toFront();
                frame.setAlwaysOnTop(false);
            }
        }
    }



//== OTHER NON-PRIVATE INSTANCE METHODS ========================================

    /***************************************************************************
     * Clears the canvas, it means that it erases all the shapes on the canvas.
     */
    public void clear()
    {
        erase(new Rectangle2D.Double(0, 0, width, height));
    }


    /***************************************************************************
     * Draws a line with given starting and ending point and given color.
     *
     * @param  x1    The horizontal coordinate of the start of the drawn line
     * @param  y1    The veritcal coordinate of the start of the drawn line
     * @param  x2    The horizontal coordinate of the end of the drawn line
     * @param  y2    The veritcal coordinate of the end of the drawn line
     * @param  color The color of the drawn line
     */
    public void drawLine(int x1, int y1, int x2, int y2, NamedColor color)
    {
        setForegroundColor(color);
        painter.drawLine(x1, y1, x2, y2);
        canvasPanel.repaint();
    }


    /***************************************************************************
     * Draws the given text by current font and given color.
     *
     * @param text   The drawn text.
     * @param x      The horizontal coordinate of the drawn text
     * @param y      The veritcal coordinate of the drawn text
     * @param color  The color of the drawn text
     */
    public void drawString(String text, int x, int y, NamedColor color)
    {
        setForegroundColor(color);
        painter.drawString(text, x, y);
        canvasPanel.repaint();
    }


    /***************************************************************************
     * Erases the given shape. However its instance still exist,
     * it is only not visible, because it is panted by background color.
     *
     * @param shape Shape which should be erased.
     */
    public void erase(Shape shape)
    {
        java.awt.Color original = painter.getColor();
        painter.setColor(backgroundColor.getAwtColor());
        painter.fill(shape);
        painter.setColor(original);
        canvasPanel.repaint();
    }


    /***************************************************************************
     * Paints the given shape and fiil it by the canvas color.
     *
     * @param shape Shape which should be painted.
     */
    public void fill(Shape shape)
    {
        painter.fill(shape);
        canvasPanel.repaint();
    }


    /***************************************************************************
     * Retuns the string representation of canvas.
     * It is used mainly by debugging.
     *
     * @return The string representation of the canvas.
     */
    @Override
    public String toString()
    {
        return this.getClass().getName() +
            "(" + width + "x" + height +
            " points, background color=" + backgroundColor + ")";
    }



//== PRIVATE AND AUXILIARY CLASS METHODS =======================================
//== PRIVATE AND AUXILIARY INSTANCE METHODS ====================================

    /***************************************************************************
     * Initialize some parameters from the configuration file.
     * This file is placed in the user home directory
     * in the folder {@code .rup} in the file {@code bluej.properties}.
     * It is meant primary for teachers to make easy placing the window
     * by working with several displays, but only one visible by students.
     */
    private void configurationFromFile() {
        Properties sysProp = System.getProperties();
        String     userDir = sysProp.getProperty("user.home");
        File       rupFile = new File(userDir, ".rup/bluej.properties");
        Properties rupProp = new Properties();
        try {
            Reader reader = new FileReader(rupFile);
            rupProp.load(reader);
            reader.close();
            String sx = rupProp.getProperty("canvas.x");
            String sy = rupProp.getProperty("canvas.y");
            int x = Integer.parseInt(rupProp.getProperty("canvas.x"));
            int y = Integer.parseInt(rupProp.getProperty("canvas.y"));
            position = new Point(x, y);
        }catch(Exception e)  {
            position = new Point(0, 0);
        }
    }


    /***************************************************************************
     * Prepares the image, on which the all shapes will be painted.
     */
    private void prepareImage()
    {
        canvasImage = canvasPanel.createImage(width, height);
        painter = (Graphics2D)canvasImage.getGraphics();
        painter.setColor(backgroundColor.getAwtColor());
        painter.fillRect(0, 0, width, height);
        painter.setColor(java.awt.Color.BLACK);
    }


    /***************************************************************************
     * Set only the size of the canvas.
     * It is a private version intended only for the constructor.
     * The public version add making the window visible and preparing the image.
     *
     * @param width  The set canvas width in points
     * @param height The set canvas height in points
     */
    private void setSizePrivate(int width, int height)
    {
        boolean toCorrect;
        Dimension dim;
        Insets    ins;
        do {
            this.width = width;
            this.height = height;
            frame.setResizable(true);
            canvasPanel.setPreferredSize(new Dimension(width, height));
            frame.pack();
            dim = frame.getSize();
            ins = frame.getInsets();
//            IO.inform(
//                   "I set : width=" + width + ", height=" + height +
//                 "\nI have: width=" + dim.width + ", height=" + dim.height +
//                 "\nleft=" + ins.left + ", right=" + ins.right +
//                 "\n top=" + ins.top + ", bottom=" + ins.bottom);
            toCorrect = false;
            if (width < (dim.width - ins.left - ins.right)) {
                width  = dim.width - ins.left - ins.right + 2;
                toCorrect= true;
            }
            if (height < (dim.height - ins.top - ins.bottom)) {
                height  = dim.height - ins.top - ins.bottom;
                toCorrect= true;
            }
        } while (toCorrect);

        frame.setResizable(false);    //Není možné měnit rozměr pomocí myši
        IO.Correction.windowSize(frame);
    }



//== EMBEDDED AND INNER CLASSES ================================================
//== TESTING CLASSES AND METHODS ===============================================
//
//    /***************************************************************************
//     * Test method.
//     */
//    public static void test()
//    {
//        Canvas instance = Canvas.getCanvas();
//        IO.inform("Redy");
//
//        Object o;   //Editor warnings shut up
//        o = new Rectangle (0, 0, 300, 300);
//        o = new Ellipse   (0, 0, 300, 300);
//        o = new Triangle  (0, 0, 300, 300);
//        IO.inform("Painted");
//
//        System.exit(0);
//    }
//    /** @param args Command line arguments - not used. */
//    public static void main(String[] args)  {  test();  }
//
}

//%%- ---- Start of the ovejumped text with closing of the envelope ----
////////////////////////////////////////////////////////////////////////////////
//== TESTING CLASSES AND METHODS ===============================================
//
//    private static final long TIME = System.currentTimeMillis();
//    private static final List<String> gap = new ArrayList<String>();
//    static { gap.add(""); }
//
//    // Define/create thread local variable
//    private static final ThreadLocal<Integer> threadLevel =
//                                              new ThreadLocal<Integer>();
//    private static final ThreadLocal<Character> threadSign =
//                                                new ThreadLocal<Character>();
//    static {
//        threadLevel.set(0);
//        threadSign .set('-');
//    }
//
////    private static int level = 0;
//
//    /***************************************************************************
//     *
//     * @param text
//     */
//    private static synchronized void msgS(String text)
//    {
//        msg(text);
//        int level = threadLevel.get() + 1;
//        if (level >= gap.size()) {
//            gap.add(gap.get(level-1) + "- ");
//        }
//        threadLevel.set(level);
//    }
//
//
//    /***************************************************************************
//     *
//     * @param text
//     */
//    private static synchronized void msgF(String text)
//    {
//        threadLevel.set(threadLevel.get() - 1);
//        msg(text);
//    }
//
//
//    /***************************************************************************
//     *
//     * @param text
//     */
//    private static synchronized void msg(String text)
//    {
//        double time  = (System.currentTimeMillis() - TIME) / 1000.0;
//        int    level = threadLevel.get();
//        char   sign  = threadSign.get();
//        System.out.printf("%8.3f %c %s%s%n", time, sign, gap.get(level), text);
//    }
//

    /***************************************************************************
     * Test method.
     */
    public static void test()
    {
        Canvas instance = Canvas.getCanvas();
        IO.inform("Ready");

        Object o;   //Editor warnings shut up
        o = new Rectangle (0, 0, 300, 300);
        o = new Ellipse   (0, 0, 300, 300);
        o = new Triangle  (0, 0, 300, 300);
        IO.inform("After you feast your eyes, press OK");
        System.exit(0);
    }
    /** @param args Command line arguments - they are not used. */
    public static void main(String[] args)  {  test();  }
}
